#ifndef Espressif_Updater_h
#define Espressif_Updater_h

// Local include.
#include "Configuration.h"

#if THINGSBOARD_USE_ESP_PARTITION

// Local include.
#include "IUpdater.h"

// Library include.
#include <esp_ota_ops.h>

constexpr char INVALID_OTA_PARTIION[] = "The running partition and the parition we wanted to boot into were not the same meaning the previous update failed and choose the fallback partition instead";
constexpr char MISSING_OTA_APP[] = "Missing second ota app or app was invalid";
constexpr char BEGIN_UPDATE_FAILED[] = "Beginning update failed with error reason (%s)";


/// @brief IUpdater implementation that uses the Over the Air Update API from Espressif (https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/ota.html)
/// under the hood to write the given binary firmware data into flash memory so we can restart with newly received firmware
/// @tparam Logger Implementation that should be used to print error messages generated by internal processes and additional debugging messages if THINGSBOARD_ENABLE_DEBUG is set, default = DefaultLogger
template <typename Logger = DefaultLogger>
class Espressif_Updater : public IUpdater {
  public:
    Espressif_Updater() = default;

    bool begin(size_t const & firmware_size) override {
        esp_partition_t const * running = esp_ota_get_running_partition();
        esp_partition_t const * configured = esp_ota_get_boot_partition();

        if (configured != running) {
            Logger::printfln(INVALID_OTA_PARTIION);
            return false;
        }

        esp_partition_t const * update_partition = esp_ota_get_next_update_partition(nullptr);

        if (update_partition == nullptr) {
            Logger::printfln(MISSING_OTA_APP);
            return false;
        }

        // Temporary handle is used, because it allows using a void* as the actual ota_handle,
        // allowing us to only include the esp_ota_ops header in the defintion (.cpp) file,
        // instead of also needing to declare it in the declaration (.h) header file
        esp_ota_handle_t ota_handle;
        esp_err_t const error = esp_ota_begin(update_partition, firmware_size, &ota_handle);

        if (error != ESP_OK) {
            Logger::printfln(BEGIN_UPDATE_FAILED, esp_err_to_name(error));
            return false;
        }

        m_ota_handle = ota_handle;
        m_update_partition = update_partition;
        return true;
    }

    size_t write(uint8_t * payload, size_t const & total_bytes) override {
        esp_err_t const error = esp_ota_write(m_ota_handle, payload, total_bytes);
        size_t const written_bytes = (error == ESP_OK) ? total_bytes : 0U;
        return written_bytes;
    }

    void reset() override {
#if defined(ESP8266) || (ESP_IDF_VERSION_MAJOR == 4 && ESP_IDF_VERSION_MINOR < 3) || ESP_IDF_VERSION_MAJOR < 4
        (void)end();
#else
        (void)esp_ota_abort(m_ota_handle);
#endif
    }

    bool end() override {
        esp_err_t error = esp_ota_end(m_ota_handle);
        if (error != ESP_OK) {
            return false;
        }

        error = esp_ota_set_boot_partition(m_update_partition);
        return error == ESP_OK;
    }

  private:
    uint32_t               m_ota_handle = {};       // ESP OTA hanle that is used to to access the underlying updater
    esp_partition_t const *m_update_partition = {}; // Non active OTA partition that we write our data into
};

#endif // THINGSBOARD_USE_ESP_PARTITION

#endif // Espressif_Updater_h
